﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace BuzzX8.Xmpp.StreamElements.Builders
{
    public abstract class StreamElementBuilder<T> : StreamElementBuilder where T : StreamElement
    {
        private IEnumerable<string> supportedElementNames;
        private T rootElement;
        private Stack<string> namesStack = new Stack<string>();

        protected T RootElement
        {
            get { return rootElement; }
        }

        protected string CurrentNodeName { get; private set; }

        protected string CurrentNodeNamespace { get; private set; }

        protected int Depth
        {
            get { return namesStack.Count; }
        }

        protected bool IsRootLevel
        {
            get { return Depth == 0; }
        }

        public override StreamElement Element
        {
            get { return rootElement; }
        }

        public override bool CanBuildElement(string elementName)
        {
            if (supportedElementNames == null) supportedElementNames = GetSupportedElementNames();
            return supportedElementNames.Any(n => n == elementName);
        }

        protected abstract IEnumerable<string> GetSupportedElementNames();

        public T Get()
        {
            return rootElement;
        }

        public override void AddAttribute(string name, string value)
        {
            if (name == "xmlns") CurrentNodeNamespace = value;
            if (IsRootLevel) AddAttributeToRootElement(name, value);
            else AddAttributeToCurrentNode(name, value);
        }

        protected abstract void AddAttributeToRootElement(string name, string value);

        protected abstract void AddAttributeToCurrentNode(string name, string value);

        public override void StartNode(string name)
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
            if (string.IsNullOrEmpty(CurrentNodeName)) throw new InvalidOperationException();
            namesStack.Push(CurrentNodeName);
            CurrentNodeName = name;
            CurrentNodeNamespace = "";
        }

        public override void SetNodeValue(string value)
        {
            if (IsRootLevel) SetRootElementValue(value);
            else SetCurrentNodeValue(value);
        }

        protected virtual void SetRootElementValue(string value)
        { }

        protected virtual void SetCurrentNodeValue(string value)
        { }

        public override void EndNode()
        {
            if (IsRootLevel) throw new InvalidOperationException();
            CurrentNodeName = namesStack.Pop();
            CurrentNodeNamespace = "";
        }

        public override void StartNewElement(string name)
        {
            if (!CanBuildElement(name)) throw new ArgumentException("Unsupported element!!!");
            rootElement = CreateNewElement(name);
            CurrentNodeName = name;
            namesStack.Clear();
        }

        protected abstract T CreateNewElement(string name);
    }
}
